package com.droidski.taskdb.cli;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Properties;

public abstract class MainCLI {
	
	protected CLI cli;
	private List<Method> _annotatedMethods;
	int longestMethodName = 0;
	Properties properties = new Properties();

	public static void out(String msg) {
		System.out.println(msg);
	}

	public static void err(String msg) {
		System.err.println(msg);
	}
	
	public static void die(String msg) {
		err(msg);
		System.exit(1);
	}
	
	public static void dieIfNull(String valueToCheck, String msg) {
		if ( valueToCheck == null ) {
			die(msg);
		}
	}

	public MainCLI(String[] args) throws Exception {
		cli = new CLI(args);
		_init();
		if ( args.length == 0 ) {
			usageAndDie();
		}
		cli = new CLI(args);
		if ( cli.args.length == 0 ) {
			usageAndDie();
		}
	}

	public boolean execute(String line) throws Exception {
		String[] lines = line.split(";");
		for ( String l : lines ) {
			cli = new CLI(l.trim());
			if ( !execute() ) {
				return false;
			}
		}
		return true;
	}
	
	public boolean execute() throws Exception {
		String command = cli.args[0];
		Method m = getMethod(command);
		if ( m == null ) {
			if ( "".equals(command) ) {
				usageAndDie();
			} else {
				die("No such command '" + command + "' - try 'help'");
			}
			
		} else {
			Object result = m.invoke(this);
			if ( result instanceof Boolean ) {
				return (Boolean)result;
			} else {
				return true;
			}
		}
		return false;
	}
	
	private Method getMethod(String command) {
		for ( Method m : _annotatedMethods ) {
			Command cmd = m.getAnnotation(Command.class);
			if ( cmd.name().equals(command)) {
				return m;
			}
		}
		return null;
	}

	private void _init() {

		List<Method> results = new ArrayList<Method>();
		Method[] methods = getClass().getMethods();
		for ( Method method : methods ) {
			Command cmd = (Command)method.getAnnotation(Command.class);
			if ( cmd != null ) {
				results.add(method);
				longestMethodName = longestMethodName > cmd.name().length() ? longestMethodName : cmd.name().length();
			}
		}
		_annotatedMethods = results;
		
		Collections.sort(_annotatedMethods, new Comparator<Method>() {

			@Override
			public int compare(Method m1, Method m2) {
				
				Command c1 = m1.getAnnotation(Command.class);
				Command c2 = m2.getAnnotation(Command.class);
				
				int groupCompare = c1.group().compareTo(c2.group());
				if ( groupCompare == 0 ) {
					return c1.name().compareTo(c2.name());
				} else {
					return groupCompare;
				}
			}
		});

	}

	@Command(name="put", description="stores a variable", group="script")
	public final void put() {
		String key = getCommandValue();
		String value = cli.getValue(key);
		properties.setProperty(key, value);
	}

	@Command(name="fail", description="fails the script with an optional message", group="script")
	public final boolean fail() {
		String msg = getCommandValue();
		err(msg);
		return false;
	}

	@Command(name="get", description="prints a variable", group="script")
	public void get() {
		String key = getCommandValue();
		String value = properties.getProperty(key);
		out(value);
	}

	@Command(name="usage", description="lists the allowable commands", group="help")
	public final void usage() {
		err("Usage: ");
		for ( Method m : _annotatedMethods ) {
			Command cmd = m.getAnnotation(Command.class);
			String padding = "";
			if ( cmd.name().length() < longestMethodName ) {
				int size = longestMethodName - cmd.name().length();
				for ( int i=0; i<size; i++) {
					padding += " ";
				}
			}
			out(" " + cmd.name() + padding + "    " + cmd.description());
		}
	}
	
	public abstract String getExecutableName();

	@Command(name="echo", description="echos a string to the stdout", group="script")
	public final void echo() {
		out(cli.toString());
	}
		
	@Command(name="help", description="Displays detailed help on a command", group="help")
	public final void help() {
		boolean helped = false;
		if ( cli.args.length < 2 ) {
			die("Usage: " + getExecutableName() + " help <<COMMAND>>");
		}
		String command = cli.args[1];
		for ( Method m : _annotatedMethods ) {
			Command cmd = m.getAnnotation(Command.class);
			if ( cmd.name().equals(command) ) {
				out(cmd.help());
				helped = true;
				break;
			}
		}
		if ( !helped ) {
			die("No such command " + command);
		}
	}

	@Command(name="sleep", 
			description="performs a Thread.sleep for the specified ms",
			help="Usage: task sleep <<MILLIS>>",
			group="script")
	public final boolean sleep() throws Exception {
		if ( cli.args.length < 2 ) {
			die("Usage: task sleep <<FILE>>");
			return false;
		} else {
			try {
				long sleepFor = Long.parseLong(getCommandValue());
				Thread.sleep(sleepFor);
				return true;
			} catch (Exception e) {
				err(e.getMessage());
				return false;
			}
		}
	}
		
	@Command(name="script", 
			description="executes a scripted set of commands",
			help="Usage: task script <<FILE>>",
			group="script")
	public final void script() throws Exception {
		if ( cli.args.length < 2 ) {
			die("Usage: taskdb script <<FILE>>");
		}
		String fileName = getCommandValue();
		File file = getFileOrDie(fileName);
		BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
		String line = reader.readLine();
		int lineNo = 0;
		while ( line != null ) {
			lineNo++;
			boolean result = execute(line);
			if ( result ) {
				line = reader.readLine();
			} else {
				die("Failed " + file + ", line " + lineNo + ": '" + line + "'");
			}
		}
	}

	private final void usageAndDie() {
		usage();
		System.exit(1);
	}

	protected final File getFileOrDie(String fileName) {
		if ( fileName == null ) {
			die("No file specified.");
		}
		File file = new File(fileName);
		if ( !file.exists() ) {
			die("No such file : " + file);
		}
		return file;
	}

	/**
	 * returns the command currently executing
	 * @return
	 */
	public final String getCommand() {
		return cli.args[0];
	}
	
	/**
	 * returns the parameter passed after the command parameter
	 * @return
	 */
	public final String getCommandValue() {
		if ( cli.args.length > 1 ) {
			return cli.args[1];
		} else {
			return null;
		}
	}
	
}
